package org.ling.xuan.common;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson.JSONObject;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import lombok.extern.slf4j.Slf4j;
import org.ling.xuan.config.SysConstant;
import org.ling.xuan.config.SystemProperties;
import org.ling.xuan.context.AddPipeLineContext;
import org.ling.xuan.context.CheckPipelineContext;
import org.ling.xuan.dao.FileDao;
import org.ling.xuan.dao.LocalAddDao;
import org.ling.xuan.dao.MarkFileDao;
import org.ling.xuan.dao.ProjectDao;
import org.ling.xuan.dialog.EmailPreViewDialog;
import org.ling.xuan.entity.AddInfoContext;
import org.ling.xuan.entity.HundSonInfo;
import org.ling.xuan.entity.LxFile;
import org.ling.xuan.entity.SvnInfo;
import org.ling.xuan.entity.mo.CommonSetting;
import org.ling.xuan.entity.mo.EmailInitInfo;
import org.ling.xuan.entity.mo.EmailSetting;
import org.ling.xuan.entity.mo.LocalAdd;
import org.ling.xuan.entity.mo.MarkFile;
import org.ling.xuan.entity.mo.ProjectSetting;
import org.ling.xuan.entity.mo.SvnCommitCheck;
import org.ling.xuan.exception.SvnException;
import org.ling.xuan.pipeline.PipeLineFactory;
import org.ling.xuan.util.CmdUtil;
import org.ling.xuan.util.FileLogUtil;
import org.ling.xuan.util.HttpDownLoadUtil;
import org.ling.xuan.util.LoggerUtil;
import org.ling.xuan.util.LxIoUtil;

import javax.mail.MessagingException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class CommonBizUtil {
    public static String getCommitMsg(Map<String, String> versionMap, Map<String,String> addPathMap, String id) {
        LocalAdd localAdd = LocalAddDao.getById(id);
        List<MarkFile> markFiles = MarkFileDao.listByAddId(id);
        List<String> unRelationFiles = markFiles.stream().filter(f -> Boolean.FALSE.equals(f.isRelation())).map(MarkFile::getFilePath).filter(FileUtil::exist).collect(Collectors.toList());
        List<String> relationFiles = markFiles.stream().filter(f -> Boolean.TRUE.equals(f.isRelation())).map(MarkFile::getFilePath).filter(FileUtil::exist).collect(Collectors.toList());
        unRelationFiles = getAddWithVersion(versionMap, addPathMap, unRelationFiles);
        relationFiles = getAddWithVersion(versionMap, addPathMap, relationFiles);
        String emailEx = "[需求编号]{}\n" +
                "[需求引入行]{}\n" +
                "[需求描述]{}\n" +
                "[修改单编号]{}\n" +
                "[当前可提交版本]{}\n" +
                "[数据库修改]无\n" +
                "[参数文件修改]无\n" +
                "[接口文件修改]无\n" +
                "[修改关联版本]\n" +
                "{}\n" +
                "[升级关联版本]\n{}\n";
        return StrUtil.format(emailEx,
                localAdd.getRequirementId(),
                localAdd.getRequireBank(),
                localAdd.getRequirementDes(),
                localAdd.getModifyId(),
                localAdd.getModifyVersion(),
                StrUtil.join("\n", unRelationFiles), StrUtil.join("\n", relationFiles));
    }


    /**
     * 获取带版本的添加路径
     * @param versionMap 版本map
     * @param addPathMap 添加路径map
     * @param localFiles 本地文件
     * @return {@link List}<{@link String}>
     */
    private static List<String> getAddWithVersion(Map<String, String> versionMap, Map<String, String> addPathMap, List<String> localFiles) {
        localFiles = localFiles.stream().filter(addPathMap::containsKey).map(f->{
            String path = addPathMap.get(f);
            if (versionMap.containsKey(f)&& StrUtil.isNotBlank(versionMap.get(f))) {
                return path + " V" + versionMap.get(f);
            } else {
                return path;
            }
        }).collect(Collectors.toList());
        return localFiles;
    }

    public static Map<String, ProjectSetting> getFileSettingMap(List<String> files) {
        Map<String, ProjectSetting> map = new HashMap<>();
        List<ProjectSetting> settings = ProjectDao.listAll();
        for (String file : files) {
            for (ProjectSetting ps : settings) {
                if (file.startsWith(ps.getProjectPath())) {
                    map.put(file, ps);
                    continue;
                }
            }
        }
        return map;
    }

    public static List<SvnCommitCheck> getSvnCommitCheck(Map<String,ProjectSetting> projectSettingMap, List<String> filePaths) {
        List<SvnCommitCheck> svnCommitChecks = new ArrayList<>();

        CommonSetting commonSetting = FileDao.getCommonSetting();
        for (String file : filePaths) {
            ProjectSetting projectSetting = projectSettingMap.get(file);
            svnCommitChecks.add(svnCheck(file, commonSetting, projectSetting));
        }
        return svnCommitChecks;
    }

    public static SvnCommitCheck svnCheck(String file, CommonSetting commonSetting, ProjectSetting ps) {
        SvnCommitCheck svnCommitCheck = new SvnCommitCheck(file);
        if (!FileUtil.exist(file)) {
            svnCommitCheck.error(StrUtil.format("文件不存在"));
        } else {
            if (ps == null) {
                svnCommitCheck.error(StrUtil.format("项目设置文件未找到"));
            } else {
                svnCommitCheck.setProjectPath(ps.getProjectPath());
                svnCommitCheck.setUnionCommitPath(ps.getUnionCommitPath());
                if (StrUtil.isNotBlank(ps.getSvnPre())) {
                    svnCommitCheck.setSvnFile(true);
                    LxFile lxFile = getLxFile(file, commonSetting, ps);
                    svnCommitCheck.setModifyStatus(lxFile.getSvnStatus());
                    if (lxFile.isRemoteExist()) {
                        if ("?".equals(lxFile.getSvnStatus()) || "A".equals(lxFile.getSvnStatus())) {
                            svnCommitCheck.error("远程文件已存在，本地状态未更新");
                        } else if (StrUtil.isBlank(lxFile.getSvnStatus())) {
                            svnCommitCheck.warn("本地文件未更新，不需要提交");
                        } else if ("C".equals(lxFile.getSvnStatus())) {
                            svnCommitCheck.error("本地文件冲突中,请先解决");
                        } else {
                            if (lxFile.getLocalSvnInfo().getLastChangeVersion().equals(lxFile.getRemoteSvnInfo().getLastChangeVersion())) {
                                svnCommitCheck.success("版本检测通过，可以提交");
                            } else {
                                svnCommitCheck.error("服务器文件已被更新，请先本地同步版本");
                            }
                        }
                    } else {
                        svnCommitCheck.success("服务器文件不存在，可以提交");
                    }
                } else {
                    svnCommitCheck.warn("项目未设置svn");
                }
            }
        }
        return svnCommitCheck;
    }

    public static LxFile getLxFile(String filePath, CommonSetting cs, ProjectSetting ps) {
        LxFile lxFile = new LxFile();
        lxFile.setLocalPath(filePath);
        if (StrUtil.isNotBlank(ps.getSvnPre())) {
            lxFile.setSvnFile(true);
            try {
                String s = CmdUtil.executeCommand(ps.getProjectPath(), "svn status " + filePath);
                if (StrUtil.isNotBlank(s)) {
                    s = s.replace("\r\n", "\n").trim();
                    if (s.startsWith("---")) {
                        //---  Changelist 'Default Changelist':
                        s = s.substring(s.indexOf("\n") + 1);
                    }
                    s = s.trim().substring(0, 1);
                }
                FileLogUtil.log("svn status : {},{}", FileUtil.getName(filePath), s);
                lxFile.setSvnStatus(s);
                lxFile.setSvnUrl(filePath.replace(ps.getProjectPath(), ps.getSvnPre()));
                SvnInfo localSvn = new SvnInfo(CmdUtil.executeCommand(ps.getProjectPath(), "svn info " + filePath));
                lxFile.setLocalSvnInfo(localSvn);
                boolean exist = HttpDownLoadUtil.exist(lxFile.getSvnUrl(), cs.getUserName(), cs.getPassword());
                lxFile.setRemoteExist(exist);
                if (lxFile.isRemoteExist()) {
                    SvnInfo remoteSvn = new SvnInfo(CmdUtil.executeCommand(ps.getProjectPath(), "svn info " + lxFile.getSvnUrl()));
                    lxFile.setRemoteSvnInfo(remoteSvn);
                }
            } catch (Exception e) {
                FileLogUtil.log("获取svn信息发送异常:{}",e);
                throw new SvnException("获取svn信息发送异常,请查看文件日志");
            }
        } else {
            lxFile.setSvnFile(false);
        }
        //FileLogUtil.log(JSONObject.toJSONString(lxFile));
        return lxFile;
    }

    public static boolean addAndCommit(Project project, List<SvnCommitCheck> filePaths, String msg) throws IOException, InterruptedException {
        boolean success = false;
        List<SvnCommitCheck> unUnions = filePaths.stream().filter(f -> StrUtil.isBlank(f.getUnionCommitPath())).collect(Collectors.toList());
        List<SvnCommitCheck> unions = filePaths.stream().filter(f -> StrUtil.isNotBlank(f.getUnionCommitPath())).collect(Collectors.toList());
        for (SvnCommitCheck f : filePaths) {
            if (f.getModifyStatus().equals("?")) {
                try {
                    String relativePath = f.getFilePath().substring(f.getProjectPath().length() + 1);
                    String s = CmdUtil.executeCommand(f.getProjectPath(), "svn add " + relativePath);
                    LoggerUtil.log(project, s);
                } catch (Exception e) {
                    Messages.showWarningDialog(e.getMessage(), "提示");
                    success = false;
                }
            }
        }
        Map<String, List<SvnCommitCheck>> unUnionMap = new HashMap<>();
        for (SvnCommitCheck f : unUnions) {
            List<SvnCommitCheck> ss = unUnionMap.getOrDefault(f.getProjectPath(), new ArrayList<>());
            ss.add(f);
            unUnionMap.put(f.getProjectPath(), ss);
        }
        for (String k : unUnionMap.keySet()) {
            try {
                List<String> pss = CollUtil.map(unUnionMap.get(k), f -> f.getFilePath(), true);
                String s = CmdUtil.executeCommand(k, "svn commit " + StrUtil.join(",", pss) + " -m '" + msg + "'");
                LoggerUtil.log(project, s);
                success = true;
            } catch (Exception e) {
                Messages.showWarningDialog(e.getMessage(), "提示");
                success = false;
            }
        }
        Map<String, List<SvnCommitCheck>> unionMap = new HashMap<>();
        for (SvnCommitCheck f : unions) {
            List<SvnCommitCheck> ss = unUnionMap.getOrDefault(f.getProjectPath(), new ArrayList<>());
            ss.add(f);
            unionMap.put(f.getProjectPath(), ss);
        }
        for (String k : unionMap.keySet()) {
            if (FileUtil.exist(k + "/.svn")) {
                try {
                    List<String> pss = CollUtil.map(unUnionMap.get(k), f -> f.getFilePath(), true);
                    String s = CmdUtil.executeCommand(k, "svn commit " + StrUtil.join(",", pss) + " -m '" + msg + "'");
                    LoggerUtil.log(project, s);
                    success = true;
                } catch (Exception e) {
                    Messages.showWarningDialog(e.getMessage(), "提示");
                    success = false;
                }
            } else {
                String errorMsg = StrUtil.format("统一路径{}下未找到.svn文件", k);
                LoggerUtil.log(project, errorMsg);
                Messages.showWarningDialog(errorMsg, "提示");
                success = false;
            }
        }
        return success;
    }

    public static boolean sendAddEmail(Project project, String id) throws MessagingException, IOException {
        boolean result = false;
        LocalAdd localAdd = LocalAddDao.getById(id);
        EmailSetting emailSetting = FileDao.getEmailSetting();
        boolean f = emailSetting.checkConnect();
        if (!f) {
            LoggerUtil.log(project, "邮箱连接失败");
            Messages.showWarningDialog("邮箱连接失败,请检测邮箱配置", "错误提示");
        }
        AddInfoContext addInfoContext = FileDao.getAddInfoContext(id);
        CommonSetting commonSetting =addInfoContext.getCommonSetting();
        java.util.List<String> files = addInfoContext.getZips().stream().filter(FileUtil::exist).collect(Collectors.toList());
        String msg = addInfoContext.getAddMsg();
        EmailInitInfo emailInitInfo = new EmailInitInfo();
        emailInitInfo.setAddInfo(localAdd);
        emailInitInfo.setEmailSetting(emailSetting);
        emailInitInfo.setCommonSetting(commonSetting);
        emailInitInfo.setFiles(files);
        emailInitInfo.setMsg(msg);
        emailInitInfo.setProject(project);
        EmailPreViewDialog dialog = new EmailPreViewDialog(project, true, emailInitInfo);
        if (dialog.showAndGet()) {
            dialog.onOk();
            result = true;
        }
        return result;
    }

    /**
     * 批量获取文件和其版本的映射map
     * @param cs
     * @param ps
     * @param files
     * @return {@link Map}<{@link String}, {@link String}>
     */
    public static Map<String, String> getVersionMap(CommonSetting cs,Map<String,ProjectSetting> ps,List<String> files) {
        Map<String, String> map = new HashMap<>();
       for(String file:files){
           if(ps.containsKey(file)) {
               if (file.endsWith(".java")) {
                   HundSonInfo hundSonInfo = new HundSonInfo(file,cs.getAuth(),ps.get(file).getSystemName());
                   if(StrUtil.isNotBlank(hundSonInfo.getLastAnnotationVersion())) {
                       map.put(file, hundSonInfo.getLastAnnotationVersion());
                   }
               }
           }
       }
        return map;
    }

    /**
     * 批量获取文件和其添加路径的映射map
     * @param settingMap
     * @param files
     * @return {@link Map}<{@link String}, {@link String}>
     */
    public static Map<String, String> getAddPathMap(Map<String, ProjectSetting> settingMap, List<String> files) {
        Map<String, String> map = new HashMap<>();
        for(String f:files){
            if(settingMap.containsKey(f)){
                ProjectSetting ps  = settingMap.get(f);
               String p = f.replace(ps.getLocalPre(),ps.getAddPre());
               map.put(f,p);
            }
        }
        return map;
    }


    public static boolean add(Project project,String od) {
        LocalAdd localAdd = LocalAddDao.getById(od);
        List<String> zips= new ArrayList<>();
        List<String> fileReplicate = new ArrayList<>();
        List<MarkFile> markFiles = MarkFileDao.listByAddId(od);
        List<String> modifyFiles = markFiles.stream().filter(f->!f.isRelation()).map(MarkFile::getFilePath).filter(FileUtil::exist).collect(Collectors.toList());
        CommonSetting commonSetting = FileDao.getCommonSetting();
        List<String> files = markFiles.stream().map(MarkFile::getFilePath).filter(FileUtil::exist).collect(Collectors.toList());
        Map<String, ProjectSetting> settingMap = CommonBizUtil.getFileSettingMap(files);
        //获取版本对应关系
        Map<String,String> versionMap = CommonBizUtil.getVersionMap(commonSetting,settingMap,files);
        //获取添加路径映射关系
        Map<String,String> addPathMap = CommonBizUtil.getAddPathMap(settingMap,files);
        //打包zip
        packZip(modifyFiles, od,settingMap,addPathMap,zips,fileReplicate);
        //生成添加信息
        String msg = CommonBizUtil.getCommitMsg(versionMap,addPathMap, od);
        //写入添加信息
        AddInfoContext addInfoContext = new AddInfoContext();
        addInfoContext.setLocalAdd(localAdd);
        addInfoContext.setAddMsg(msg);
        addInfoContext.setAddPathMap(addPathMap);
        addInfoContext.setCommonSetting(commonSetting);
        addInfoContext.setMarkFiles(markFiles);
        addInfoContext.setSettingMap(settingMap);
        addInfoContext.setVersionMap(versionMap);
        addInfoContext.setZips(zips);
        addInfoContext.setFileReplicatePath(fileReplicate);
        FileDao.writeAddInfoContext(addInfoContext, od);
        LoggerUtil.log(project,msg);
        return true;
    }

    private static void packZip(List<String> modifyFiles, String addId,Map<String,ProjectSetting> ps, Map<String,String> addPathMap,List<String> zips,List<String> fileReplicateList) {
        String root = LxIoUtil.getWorkHome() + "/.lx/add/" + addId;
        FileUtil.del(root);
        FileUtil.mkdir(root);
        Map<String, String> zipPreMap = new HashMap<>();
        for (String markFile : modifyFiles) {
            if(FileUtil.exist(markFile)&&ps.containsKey(markFile)&&addPathMap.containsKey(markFile)) {
                ProjectSetting projectSetting = ps.get(markFile);
                String f = root + addPathMap.get(markFile);
                FileUtil.copyFile(markFile, f);
                fileReplicateList.add(f);
                zipPreMap.put(root + projectSetting.getAddPre(), root + projectSetting.getZipPre());
            }
        }
        zipPreMap.forEach((k, v) -> {
            String name = FileUtil.getName(k);
            String zipPath = v + "/" + name + ".zip";
            ZipUtil.zip(k, zipPath, SystemProperties.isTrueDefaultFalse(SysConstant.packIncludeFolder));
            zips.add(zipPath);
        });
    }

    public static int checkFiles(Project project, List<String> modifyFiles,LocalAdd localAdd) {
        Collections.sort(modifyFiles);
        List<AddPipeLineContext> contexts = new ArrayList<>();
        CommonSetting cs = FileDao.getCommonSetting();
        Map<String,ProjectSetting> ps = getFileSettingMap(modifyFiles);
        for (String f : modifyFiles) {
            if(ps.containsKey(f)) {
                contexts.add(PipeLineFactory.buildContext(project, ps.get(f), cs, f, localAdd));
            }
        }
        for (AddPipeLineContext context : contexts) {
            PipeLineFactory.pipelineInstance().start(context);
        }
        int warnSize = 0;
        int errorSize = 0;
        Map<String, List<String>> errorFile = new HashMap<>();
        Map<String, List<String>> warnFile = new HashMap<>();
        for (CheckPipelineContext context : contexts) {
            List<String> warnMessage = new ArrayList<>();
            List<String> errorMessage = new ArrayList<>();
            for (int i = 0; i < context.getCheckStatus().size(); i++) {
                if (context.getCheckStatus().get(i) == 0) {
                    errorSize++;
                    errorMessage.add(context.getMessages().get(i));
                } else if (context.getCheckStatus().get(i) == 2) {
                    warnSize++;
                    warnMessage.add(context.getMessages().get(i));
                }
            }
            if (errorMessage.size() > 0) {
                errorFile.put(context.getFilePath(), errorMessage);
            }
            if (warnMessage.size() > 0) {
                warnFile.put(context.getFilePath(), warnMessage);
            }
        }
        LoggerUtil.log(project, "------------------检查结果-------------------------");
        LoggerUtil.log(project, "警告数量:{},错误数量:{}", warnSize, errorSize);
        List<String> errorFiles = CollUtil.newArrayList(errorFile.keySet());
        Collections.sort(errorFiles);
        List<String> warnFiles = CollUtil.newArrayList(warnFile.keySet());
        Collections.sort(warnFiles);
        if (warnFiles.size() > 0) {
            LoggerUtil.log(project, "警告文件:");
            warnFiles.forEach(f -> LoggerUtil.log(project, "        " + f));
        }
        if (errorFiles.size() > 0) {
            LoggerUtil.log(project, "错误文件:");
            errorFiles.forEach(f -> LoggerUtil.log(project, "        " + f));
        }
        if (warnFiles.size() > 0) {
            LoggerUtil.log(project, "警告详情:");
            warnFiles.forEach(f -> {
                LoggerUtil.log(project, "------------------------{}------------------------", f);
                List<String> msg = warnFile.get(f);
                msg.forEach(p -> LoggerUtil.log(project, p));
            });
        }
        if (errorFiles.size() > 0) {
            LoggerUtil.log(project, "错误详情:");
            errorFiles.forEach(f -> {
                LoggerUtil.log(project, "------------------------{}------------------------", f);
                List<String> msg = errorFile.get(f);
                msg.forEach(p -> LoggerUtil.log(project, p));
            });
        }
        if (errorSize > 0) {
            return 0;
        } else if (warnSize > 0) {
            return 2;
        } else {
            return 1;
        }
    }
}
